package camid.services.nameservice;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import camid.distribution.lifecycle.LifecycleScope;
import camid.distribution.lifecycle.LocationScope;
import camid.distribution.lifecycle.ManagedObject;
import camid.distribution.protocol.Endpoint;


@ManagedObject ( lifecycleScope = LifecycleScope.STATIC, locationScope = LocationScope.NODE )
public class NameService implements INameService {

	public static final String NAME_SERVICE_ID = "NameService";
	
	private Map<String,Endpoint> endpointMap;
	private Map<String, List<String>> definitionMap;
	private Map<String, List<String>> reverseDefinitionMap;
	private Map<String, String> bindingMap;
	
	private static final Logger logger = LogManager.getLogger(NameService.class.getName());
	
	public NameService()
	{
		endpointMap = new HashMap<String,Endpoint> ();
		definitionMap = new HashMap<String, List<String>> ();
		bindingMap = new HashMap<String, String> ();
		reverseDefinitionMap = new HashMap<String, List<String>> ();
	}
	
	/* (non-Javadoc)
	 * @see camid.services.nameservice.INameService#registerNode(camid.distribution.protocol.Endpoint)
	 */
	public String registerNode(Endpoint localEndpoint) {
		
		String vmId = UUID.randomUUID().toString();
		endpointMap.put(vmId, localEndpoint);
		return vmId;
	}

	/* (non-Javadoc)
	 * @see camid.services.nameservice.INameService#getNodeEndpoint(java.lang.String)
	 */
	public Endpoint getNodeEndpoint(String nodeId) 
	{
		Endpoint ep = endpointMap.get(nodeId);
		return ep;
	}

	/* (non-Javadoc)
	 * @see camid.services.nameservice.INameService#registerObjectDefinition(java.lang.String, java.lang.String)
	 */
	public void registerObjectDefinition(String nodeId, String entityClassName) 
	{
		if(endpointMap.containsKey(nodeId))
		{
			if(definitionMap.containsKey(entityClassName))
			{
				List<String> nodeIds = definitionMap.get(entityClassName);
				nodeIds.add(nodeId);
				List<String> definitions = reverseDefinitionMap.get(nodeId);
				if(definitions == null)
				{
					definitions = new ArrayList<String> ();
					reverseDefinitionMap.put(nodeId, definitions);
				}
				definitions.add(entityClassName);
				logger.info("Registering definition " + nodeId + "/" + entityClassName);
			}
			else
			{
				List<String> nodeIds = new ArrayList<String> ();
				nodeIds.add(nodeId);
				this.definitionMap.put(entityClassName, nodeIds);
				List<String> definitions = new ArrayList<String>();
				definitions.add(entityClassName);
				this.reverseDefinitionMap.put(nodeId, definitions);
				logger.info("Registering definition " + nodeId + "/" + entityClassName);
			}
		}
	}

	/* (non-Javadoc)
	 * @see camid.services.nameservice.INameService#getObjectDefinitionEndpoints(java.lang.String)
	 */
	public List<Endpoint> getObjectDefinitionEndpoints(String entityClassName) {
		List<Endpoint> eps = new ArrayList<Endpoint> ();
		if( definitionMap.containsKey(entityClassName) )
		{
			List<String> nodeIds = definitionMap.get(entityClassName);
			for(String nodeId : nodeIds )
			{
				Endpoint ep = endpointMap.get(nodeId);
				eps.add(ep);
			}
		}
		return eps;
	}

	/* (non-Javadoc)
	 * @see camid.services.nameservice.INameService#bind(java.lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public Boolean bind(String nodeId, String objectId, String entityClassName) 
	{
		boolean result = false;
		if( endpointMap.containsKey(nodeId) && definitionMap.containsKey(entityClassName) )
		{
			List<String> availableIds = definitionMap.get(entityClassName);
			for(String availableId : availableIds )
			{
				if(nodeId.equals(availableId))
				{
					bindingMap.put(objectId, nodeId);
					result = true;
					logger.info("Object bound " + nodeId + "/" + entityClassName + "/" + objectId);
				}
			}
		}
		return result;
		
	}

	/* (non-Javadoc)
	 * @see camid.services.nameservice.INameService#lookup(java.lang.String)
	 */
	public Endpoint lookup(String objectId) 
	{
		Endpoint ep = null;
		if( bindingMap.containsKey(objectId))
		{
			String nodeId = bindingMap.get(objectId);
			ep = endpointMap.get(nodeId);
		}
		return ep;
	}

	/* (non-Javadoc)
	 * @see camid.services.nameservice.INameService#unbind(java.lang.String)
	 */
	public Boolean unbind(String objectId) 
	{
		boolean result = false;
		if( bindingMap.containsKey(objectId) )
		{
			bindingMap.remove(objectId);
			result = true;
		}
		
		return result;
	}

	/* (non-Javadoc)
	 * @see camid.services.nameservice.INameService#unregisterNode(java.lang.String)
	 */
	public Boolean unregisterNode(String nodeId) 
	{
		boolean result = false;
		if( endpointMap.containsKey(nodeId) )
		{
			List<String> definitions = reverseDefinitionMap.get(nodeId);
			if(definitions != null)
			{
				for(String definition : definitions)
				{
					List<String> vmIds = definitionMap.get(definition);
					for(Iterator<String> iter = vmIds.iterator(); iter.hasNext(); )
					{
						String vm = iter.next();
						if(vm.equals(nodeId))
						{
							iter.remove();
						}
					}
				}
				reverseDefinitionMap.remove(nodeId);
				
				for( Iterator<Entry<String,String>> bindingIterator = bindingMap.entrySet().iterator();
						bindingIterator.hasNext(); )
				{
					Entry<String,String> entry = bindingIterator.next();
					if(entry.getValue().equals(nodeId))
					{
						bindingIterator.remove();
					}
				}
				
			}
			endpointMap.remove(nodeId);
		}
		return result;
	}

	/* (non-Javadoc)
	 * @see camid.services.nameservice.INameService#getAllNodes()
	 */
	public List<String> getAllNodes() {
		List<String> allNodes = new ArrayList<String> ();
		for(String nodeId : this.endpointMap.keySet() )
		{
			allNodes.add(nodeId);
		}
		return allNodes;
	}

}
